
import shutil

import subprocess  
import importlib  
import json  ,sys,os
from threading import Thread
import time

class flow_ee():
    def __init__(self,dct,data,all_process_info,project_path=('1','1','1','1','0')):
        self.all_process_info=all_process_info
        self.dct=dct
        self.data=data
        self.base_path=os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
        self.flow_dir_path=os.path.join(self.base_path,'easy_edit','_server','flow_dir')
        self.project_path=project_path#用户1的目录1的项目1的流程1
        self.user_project_dir=os.path.join(self.base_path,'user_dir',self.project_path[0],'self',self.project_path[1],'projects',
                                          self.project_path[2],'flow_dir',self.project_path[3]
                                           )
        self.custom_flow_dir=os.path.join(self.user_project_dir,'custom_flow')
        self.db_dir=os.path.join(self.user_project_dir,'db_dir')
        self.run_flow_dir=os.path.join(self.user_project_dir,'run_flow')
        #创建文件夹 用于存放正在运行的组件代码
        self.create_base_dir()
        #通过flow_dir获取到组件所在文件夹并生成到运行文件 并且生成执行文件
        self._add_all()
        #TODO:根据进程使用的不同语言分别在不同进程内进行初始化
        # print("self.process_dct",self.process_dct)
        # #通过flow_dir获取到组件所在文件夹并生成到运行文件 并且生成执行文件
        # self.create_run_dir()
        # #生成执行文件 并执行
        # self.run_process()
    #get_temp_class_name
    def _获取标准组件类名(self,用户id,目录1id,目录2id,组件id,组件版本id):
        return "a"+f'_{用户id}_{目录1id}_{目录2id}_{组件id}_{组件版本id}'.replace("a","")
    #get_custom_temp_class_name
    def _获取自定义组件类名(self,项目目录1id,项目目录2id,项目id,流程id,组件id):
        return "a"+f'_{项目目录1id}_{项目目录2id}_{项目id}_{流程id}_{组件id}'.replace("a","")
    def 将回调分门别类(self,flow_dict):
        del_k=set()
        res={}
        for key in flow_dict.keys():
            if key in del_k:continue
            tree_from=flow_dict[key]['tree_from']
            if tree_from is None:continue
            tree_1=tree_from[0]
            if res.get(tree_1) is None:res[tree_1]=[]
            insert_idx=0
            
            for i in range(len(tree_from)):
                if tree_from[i] not in res[tree_1]:
                    del_k.add(tree_from[i])
                    #判断之后的
                    res[tree_1].insert(insert_idx,tree_from[i])
                insert_idx=res[tree_1].index(tree_from[i])+1
        for key in res:
            keys=copy.copy(res[key])
            for k2 in keys:
                now_idx=res[key].index(k2)#获取当前位置
                change_idx=None#是否需要调整位置
                for k3 in keys:#逐个遍历
                    if k2==k3:continue
                    k3_idx=res[key].index(k3)

                    if k3_idx<now_idx:continue
                    if change_idx is not None and k3_idx<change_idx:continue
                    outk=flow_dict[k3].get("out_kwargs")
                    if outk is None:continue
                    for k4 in outk:
                        if outk[k4].get('out_params') is None:continue
                        if k2 in outk[k4]['out_params']:
                            change_idx=k3_idx
                            break
                if change_idx is not None:
                    # 移除
                    element_to_move=res[key].pop(now_idx)
                    # 在索引为 x的元素之后插入该元素
                    res[key].insert(change_idx, element_to_move)
        return res
    #将组件的类定义初始化 然后加载到内存
    def _add_all(self):
        print("self.base_path",self.base_path)
        sys.path.append(self.base_path)
        run_dir=self.run_flow_dir+os.sep+'now_run_process'
        try:os.makedirs(run_dir)
        except:pass
        process_init={}
        process_run=''
        tree_dct=self.将回调分门别类(self.data)#根据回调对数据进行分组
        write_str={
            "import_str":"\n".join(["# -*- coding: utf-8 -*-",
                                    "import os,sys",
                                    f"sys.path.append(r'{self.base_path}')",
                                    r''
                                    r"callback_lst=[]",
                                    r"public_data={'all_obj':{}}",
                                    f"public_data['all_process_info']={self.all_process_info}",
                                    f"public_data['flow_dict']={self.data}",
                                    f"public_data['run_dir']=r'{run_dir}'",
                                    f"public_data['project_path']={self.project_path}",
                                    f"public_data['db_dir']=r'{self.db_dir}'",
                                    ]),
            "body_str":"",
            "end_str":""
        }
        进程=None
        for 组件id,组件数据 in self.data.items():
            print(组件数据['temp_id'])
            print(组件数据['version'])
            flow_dir=(组件数据['temp_id']+'_'+str(组件数据['version'])).replace("a_","").split("_")
            flow_dir=["a"+item for item in flow_dir]
            

            #如果用户自定义了组件代码 那么获取自定义的组件代码
            if 组件数据.get('change_temp')is True:
                get_dir2=self._自定义组件所在文件夹(flow_dir,组件id)
                class_name=self._获取自定义组件类名(*self.project_path,组件id)
                get_dir=self._当前要运行的自定义组件所在文件夹(flow_dir,组件id)
                try:os.makedirs(get_dir)
                except:pass
                self._copy_dir(get_dir2,get_dir)
            #否则 获取组件代码
            else: 
                # print(组件数据['flow_dir'])
                get_dir=self._标准组件所在文件夹(*flow_dir)
                # print(组件数据['flow_dir'])
                class_name=组件数据['temp_id']+'_'+str(组件数据['version'])#self._获取标准组件类名(*组件数据['flow_dir'])
            write_str['body_str']+=f"from {get_dir[len(self.base_path):].replace(os.sep,'.').strip('.')}.main import {class_name}\n"  
            write_str['body_str']+=f"#组件id:{组件id} 组件名称:{组件数据['name']}\n"  
            write_str['body_str']+=f"flow_obj_{组件id}={class_name}(public_data=public_data,temp_id={组件id})\n"  
            write_str['body_str']+=f"public_data['all_obj'][str({组件id})]=flow_obj_{组件id}\n"  
            #如果是创建进程的组件 那么
            if 组件数据.get("temp_type") =="process":
                write_str['end_str']=f"flow_obj_{组件id}.run_server(callback_lst)"
                进程=组件数据['process_id']
            #如果是该进程的回调组件 那么
            elif 组件数据.get("tree_from") is not None and (len(组件数据.get("tree_from"))==1):
                pass
                # write_str['body_str']+=f"callack_lst['all_obj'][str({组件id})]=flow_obj_{组件id}\n"  
            #如果是回调组件下面的组件 那么
            else:
                pass
        for key in tree_dct:
            callback_str=f"\ndef callback_{key}(**kwargs):\n    res_{key}=kwargs\n"
            flow_ids=tree_dct[key][1:]
            for flow_id in flow_ids:
                in_put1={}
                in_put2=""
                in_d=self.data[flow_id]['in_kwargs']
                if in_d:
                    for kkk in in_d:
                        if in_d[kkk]['in_type1']=='0':#固定参数
                            in_put1[in_d[kkk]['name']]=in_d[kkk]['v0']
                        elif in_d[kkk]['in_type1']=='1':#组件传参
                            if len(in_put2)==0:
                                before="{'"
                            else:
                                before=",'"
                            fff=in_d[kkk]['in_params']
                            in_put2=in_put2+before+in_d[kkk]['name']+f"':res_{fff[0]}['"+self.data[fff[0]]['out_kwargs'][fff[1]]['name']+"']"
                        elif  in_d[kkk]['in_type1']=='2':#配置传参
                            in_put1[in_d[kkk]['name']]=in_d[kkk]['v2_val']
                if len(in_put2)!=0:
                    in_put2+="}"
                callback_str+=f"    res_{flow_id}=flow_obj_{flow_id}.run(**{in_put1},**"+in_put2+")\n"
            write_str['body_str']+=callback_str
            in_d=self.data[key]['in_kwargs']
            in_put1={}
            for kkk in in_d:
                if in_d[kkk]['in_type1']=='0':#固定参数
                    in_put1[in_d[kkk]['name']]=in_d[kkk]['v0']
                # elif in_d[kkk]['in_type1']=='1':#组件传参
                #     if len(in_put2)==0:
                #         before="{'"
                #     else:
                #         before=",'"
                #     fff=in_d[kkk]['in_params']
                #     in_put2=in_put2+before+in_d[kkk]['name']+f"':res_{fff[0]}['"+self.data[fff[0]]['out_kwargs'][fff[1]]['name']+"']"
                elif in_d[kkk]['in_type1']=='2':#配置传参
                    in_put1[in_d[kkk]['name']]=in_d[kkk]['v2_val']
            write_str['body_str']+=f"\ncallback_input_{key}={in_put1}"
            write_str['body_str']+=f"\ncallback_input_{key}['callback']=callback_{key}"
            write_str['body_str']+=f"\ncallback_lst.append(callback_input_{key})"
        
        #要运行的python文件 路径
        run_python_path=run_dir+os.sep+'process_'+str(进程)+'.py'
        #要执行的脚本 路径
        self.windows_start_path=run_dir+os.sep+'process_'+str(进程)+".bat"
        self.linux_start_path=run_dir+os.sep+'process_'+str(进程)+".sh"
        #记录日志的log 路径
        self.log_file_path=run_dir+os.sep+'process_'+str(进程)+".log"
        #删除python文件 并重新生成
        try:os.remove(run_python_path)
        except:pass
        with open(run_python_path,'w',encoding='utf-8')as f:
            f.write(write_str['import_str']+'\n'+write_str['body_str']+'\n'+write_str['end_str'])
        #生成windows脚本文件
        with open(self.windows_start_path,'w',encoding='utf-8')as f:
                f.write('\n'.join(['@echo off',
                                    'if "%1" == "h" goto begin',
                                    'mshta vbscript:createobject("wscript.shell").run("""%~0"" h",0)(window.close)&&exit',
                                    ':begin',
                                   'setlocal',
                                   ':: 设置日志文件的路径',
                                   f'set LOG_FILE={self.log_file_path}',
                                   ':: 执行Python脚本,并将输出和错误重定向到同一个日志文件  ',
                                   f'python -u {run_python_path} > "%LOG_FILE%" 2>&1 ',
                                   ":: 结束批处理文件",
                                   "endlocal",
                                   ]))
        #生成ubuntu脚本文件
        with open(self.linux_start_path,'w',encoding='utf-8')as f:
            f.write('\n'.join(["#!/bin/bash",
                                f"(python3 -u {run_python_path} &> {self.log_file_path} &)"
                                ]))
        #启动脚本
        if self.check_windows():
            subprocess.Popen(f'start "" {self.windows_start_path}', shell=True)
        else:
            subprocess.Popen(['bash', self.linux_start_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 
            

    def check_windows(self):
        # 判断操作系统
        if os.name == 'nt':
            print("当前环境是 Windows")
            return True
        else:
            return False
    def create_base_dir(self):
        #当前流程 用户自定义组件代码 存放文件夹
        try:os.makedirs(self.custom_flow_dir)
        except:pass
        #当前流程 用户当前正在运行的代码 存放文件夹
        try:os.makedirs(self.run_flow_dir)
        except:pass
        #当前流程 当前流程建立的数据库进程 存放数据库的文件夹
        try:os.makedirs(self.db_dir)
        except:pass
    #通过flow_dir获取 此组件此版本所在目录
    def _标准组件所在文件夹(self,用户id,目录1id,目录2id,组件id,组件版本id):
        a=os.sep
        return f"{self.flow_dir_path}{a}{用户id}{a}{目录1id}{a}{目录2id}{a}{组件id}{a}history{a}{组件版本id}"
    #通过项目目录获取到用户自定义后的组件代码
    def _自定义组件所在文件夹(self,flow_dir,temp_id):
        return os.path.join(self.custom_flow_dir,*flow_dir[:4],'history',flow_dir[-1],temp_id)
    def _当前要运行的自定义组件所在文件夹(self,flow_dir,temp_id):
        return os.path.join(self.run_flow_dir,*flow_dir[:4],'history',flow_dir[-1],temp_id)
    #拷贝文件夹
    def _copy_dir(self,dir1,dir2):
        for item in os.listdir(dir1):  
            s = os.path.join(dir1, item)  
            d = os.path.join(dir2, item)  
            if os.path.isdir(s):  
                shutil.copytree(s, d, dirs_exist_ok=True)  # dirs_exist_ok=True 表示如果目标文件夹已存在则忽略错误  
            else:  
                shutil.copy2(s, d) 
    #通过flow_dir获取到组件所在文件夹并生成到运行文件 
    def create_run_dir(self):
        for key,value in self.process_dct.items():
            temp_id=value['process']
            temp_data=self.data[temp_id]
            #判断这个组件 是否是被动组件,是的话生成组件代码然后运行组件
            if temp_data['tree_type']=='0':
                #step1 生成组件代码
                #如果用户自定义了组件代码 那么获取自定义的组件代码
                if temp_data.get('change_temp')is True:
                    get_dir=self._get_flow_dir2(temp_data['flow_dir'],temp_id)
                #否则 获取组件代码
                else: 
                    get_dir=self._get_flow_dir1(temp_data['flow_dir'])
                try:
                    temp_id_dir=self.run_flow_dir+os.sep+temp_id
                    os.makedirs(temp_id_dir)
                except:pass
                # print(get_dir)
                self._copy_dir(get_dir,temp_id_dir)
import copy,platform,re



#处理当前进程  所有的回调
def sss():
    #通过判断tree_from的长度 是否为1 来判断是否是回调根函数     
    #按照tree_type 顺序进行排序
    pass







#解析完整json 将配置传参全部传入
class flow_eee():
    def start_flow(self):
        #获取到当前流程的所有数据
        self.data=copy.copy(self.all_data['flow_chat_config']['dict']['1']['dict'][self.flow_id]['flow_dict'])
        #保存当前正在运行时候 的 流程完整数据
        if self.all_data.get('run_flow') is None:
            self.all_data['run_flow']={}
        if self.all_data['run_flow'].get(self.flow_id) is None:
            self.all_data['run_flow'][self.flow_id]={"data":self.data}
        else:
            #先停止 再赋值
            self.stop_flow()
            self.all_data['run_flow'][self.flow_id]={"data":self.data}
        self.base_info=self.all_data#完整的配置文件
        
        #将当前流程需要的配置设参传入
        self.set_in_data()
        #获取到所有进程 的recv_ip 和recv_port
        self.all_process_info=self.get_all_process_info()
        #对进程进行分组#按照进程id 对组件进行分组
        self.process_dct=self.return_dct_by_process_id()
        #按照进程所属的语言进行不同处理
        for 编程语言,value in self.process_dct.items():
            new_dct={}
            for v in value.values():
                new_dct[v['process']]=self.data[v['process']]
                for k in v['child']:
                    new_dct[k]=self.data[k]
            if 编程语言=="python":
                flow_ee(value,new_dct,self.all_process_info,self.project_path)
                # self.create_process_python(value,new_dct,self.project_path)
            elif 编程语言=='node':
                pass
        self.all_data['flow_chat_config']['dict']['1']['dict'][self.flow_id]['base_info']['run_flag']=True
        send_message={
                    "type":"change_data",
                    "data_type":"set_list",#类型 修改数据
"path":"flow_chat_config.dict.1",
                    "type2":"启停流程",
                    "flow_id":self.flow_id,
                    "flow_type":True,
                    "postput":[
                        {
                        'path':f'flow_chat_config.dict.1.dict.{self.flow_id}.base_info',
                        'data':{"run_flag":True}
                        },
                    ],
                    }
        return send_message
    def check_windows(self):
        # 判断操作系统
        if os.name == 'nt':
            print("当前环境是 Windows")
            return True
        else:
            return False
    def stop_flow(self):
        print("开始关闭流程")
        try:
            all_process_info=self.all_data['run_flow'][self.flow_id]['all_process_info']
        # all_process_info[process_id]={
        #                 "recv_ip":recv_ip,
        #                 "recv_port":recv_port
        #             }
            for value in all_process_info.values():
                print("待处理进程的使用通信端口",value)
                recv_port=value['recv_port']
                self.kill_by_port(str(recv_port))
            del self.all_data['run_flow'][self.flow_id]
            self.all_data['flow_chat_config']['dict']['1']['dict'][self.flow_id]['base_info']['run_flag']=False
            send_message={
    "type":"change_data",
    "data_type":"set_list",#类型 修改数据
"path":"flow_chat_config.dict.1",
    "type2":"启停流程",
    "flow_id":self.flow_id,
    "flow_type":False,
    "postput":[
        {
        'path':f'flow_chat_config.dict.1.dict.{self.flow_id}.base_info',
        'data':{"run_flag":False}
        },
    ],
    }
            return send_message
        except:pass
        
    def window_find_pid1(self,str_):
        start_idx=str_.find("LISTENING")
        res={
            'pid':'',
            'end_idx':start_idx
        }
        try:
            str_len=len(str_)
            while res['end_idx']<str_len:
                res['end_idx']+=1
                char=str_[res['end_idx']]
                if '0' <= char <= '9':
                    res['pid']+=char
                elif char=='\n':break
        except:pass
        return res
    def window_find_pid2(self,str_):
        set_=set()
        dct=self.window_find_pid1(str_)
        while 1:
            if dct['pid']!='':
                set_.add(dct['pid']) 
                str_=str_[dct['end_idx']:]
                dct=self.window_find_pid1(str_)
            else:
                break
        return set_
    def kill_by_port(self,port):
        print("开始杀死端口",port)
        # 根据操作系统生成并执行命令  
        if self.os_name == "Windows":  
            # Windows环境下查找端口并杀死进程  
            cmd_find = f"netstat -aon | findstr :{port}"  
            process = subprocess.Popen(cmd_find, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)  
            stdout, stderr = process.communicate()  
            pid_set=self.window_find_pid2(stdout)
            for pid in pid_set:
                cmd_kill = f"taskkill /F /PID {pid}"  
                subprocess.run(cmd_kill, shell=True, check=True)  
                print(f"杀死进程{pid}")  
        else:  
            # Linux环境下查找端口并杀死进程  
            # 注意：Linux下可能需要sudo权限来杀死某些进程  
            # cmd_find = f"sudo lsof -t -i:{port}"  
            cmd_find = f"lsof -t -i:{port}"  
            process = subprocess.Popen(cmd_find, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)  
            stdout, stderr = process.communicate()  
            
            # 假设输出直接是PID  
            pid = stdout.strip()  
            if pid:  
                # cmd_kill = f"sudo kill -9 {pid}"  
                cmd_kill = f"kill -9 {pid}"  
                subprocess.run(cmd_kill, shell=True, check=True)  
                print(f"杀死{pid}")  
            else:  
                print("没有监听端口", port)
    def create_process_python(self,*a,**k):
        
        t=Thread(target=self._python_process, args=a,kwargs=k)
        t.daemon = True 
        t.start()
        t.join(0)
        print("thread...")
    def _python_process(self,value,new_dct,project_path):
        flow_ee(value,new_dct,self.all_process_info,project_path)
    # def 生成执行脚本
    def get_v2(self,path):
        res=self.base_info
        path_lst=path.split(".")
        for item in path_lst:
            if isinstance(res,list):
                res=res[int(item)]
            elif isinstance(res,dict):
                res=res[str(item)]
            else:
                res=res[int(item)]
        return copy.copy(res)
    def set_in_data(self):
        for key in self.data:
            try:
                in_kwargs=self.data[key].get('in_kwargs')
            except:
                print("--------error")
                print(self.data,key)
                time.sleep(9999)
            if in_kwargs is None:continue
            for key,value in in_kwargs.items():
                #传参名称
                name=value['name']
                #传参方式0表示固定参数 2表示配置设置 1表示组件传参
                in_type=value['in_type1']
                #如果传参类型是 通过配置进行传参 那么导入配置参数到v2内
                if in_type=='2':
                    #value['v2']是配置传参路径  v2_val配置传参内容
                    value['v2_val']=self.get_v2(value['v2'])
    def get_all_process_info(self):
        all_process_info={} 
        for value in self.data.values():
            if value.get('temp_type')=="process":#如果组件类型为创建进程的组件
                recv_ip=None
                recv_port=None
                for v2 in value['in_kwargs'].values():
                    if v2['name']=="recv_ip":
                        if v2['in_type1']=='0':#固定参数
                            recv_ip=v2['v0']
                        elif v2['in_type1']=='2':
                            recv_ip=v2['v2']
                        else:
                            raise Exception(f"进程({value['process_id']}):目前in_type1参数不支持组件传参(逻辑写起来花时间)")
                    elif v2['name']=='recv_port':
                        if v2['in_type1']=='0':#固定参数
                            recv_port=v2['v0']
                        elif v2['in_type1']=='2':
                            recv_port=v2['v2']
                        else:
                            raise Exception(f"进程({value['process_id']}):目前in_type1参数不支持组件传参(逻辑写起来花时间)")

                all_process_info[value['process_id']]={
                    "recv_ip":recv_ip,
                    "recv_port":recv_port
                }
        self.all_data['run_flow'][self.flow_id]['all_process_info']=all_process_info
        return all_process_info
    def __init__(self,all_data,flow_id,project_path):
        self.all_data=all_data
        self.flow_id=flow_id
        self.project_path=project_path
            # 检测操作系统  
        self.os_name = platform.system()  
        
    #按照进程id 对组件进行分组
    def return_dct_by_process_id(self):
        res_dct={}
        for key,value in self.data.items():
            # print(value)
            process_id=value['process_id']
            编程语言=value.get("language","python")
            if res_dct.get(编程语言) is None:
                res_dct[编程语言]={}
            #process代表创建这个进程的组件id child代表位于这个进程下的其他组件的id
            if res_dct[编程语言].get(process_id)is None:
                res_dct[编程语言][process_id]={'process':None,'child':set()}
            if value.get("temp_type")=="process":
                res_dct[编程语言][process_id]['process']=key
            else:
                res_dct[编程语言][process_id]['child'].add(key)
        return res_dct
    
         
     # 假设ee.json文件位于当前工作目录下  

if __name__=="__main__":
    file_path2 = os.path.dirname(os.path.abspath(__file__))+os.sep+'根据树结构流程字典运行流程.json'  
    # 使用with语句打开文件，确保文件正确关闭  
    with open(file_path2, 'r', encoding='utf-8') as file:  
        # 加载JSON文件内容到字典中  
        data_dict2 = json.load(file)   
    print(data_dict2)
    # 流程图数据
    # 项目完整数据
    # 项目路径
    # flow_eee(data_dict2,'1',project_path=('1','1','1','1')).stop_flow()#用户1的目录1的项目1的流程1
    flow_eee(data_dict2,'1',project_path=('1','1','1','1')).start_flow()#用户1的目录1的项目1的流程1
    while 1:pass

